import ctypes, traceback
from pynput import keyboard

def check_com_state(stage):
    COINIT_APARTMENTTHREADED = 2
    COINIT_MULTITHREADED = 0
    hr = ctypes.OleDLL('ole32').CoInitializeEx(None, COINIT_APARTMENTTHREADED)
    if hr == 0:
        print(f"[{stage}] COM: 初始化为 STA 成功")
    elif hr == 0x80010106:
        print(f"[{stage}] COM 已经初始化为其他模式 (MTA)")
        traceback.print_stack()
    else:
        print(f"[{stage}] COM 初始化返回 {hex(hr)}")

check_com_state("程序开始")
import sys
import os
import json
import platform
import serial
import winreg
from time_util import datetime_str
import serial.tools.list_ports
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMainWindow, QVBoxLayout, QMenu, QAction, QWidget, QLabel, QPushButton, QTextEdit, QDesktopWidget
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from watchdog.observers import Observer
from ws import start_server_in_thread
from config import load_config
from task_thread import ExecuteFlowThread
from serial_thread import SerialDeviceMonitorThread, SerialDeviceHandler, SerialMonitorThread

VERSION = "1.0.0"
APP_KEY = "FileSyncAssistant"


class SingleInstantce:
    
    def __init__(self, key, main_window = None):
        self.key = key
        self.server = None
        
        self.main_window = main_window
        
        self.is_runing = False
        
        #尝试连接已有的实例
        self.socket = QLocalSocket()
        self.socket.connectToServer(self.key)
        
        if self.socket.waitForConnected(100):
            # 已有实例在运行
            self.socket.write(b"show")
            self.socket.flush()
            self.socket.waitForBytesWritten(100)
            self.socket.close()
            self.is_runing = True
        else:
            self.server = QLocalServer()
            try:
                QLocalServer.removeServer(self.key)
            except:
                pass
            self.server.listen(self.key)
            self.server.newConnection.connect(self.on_new_connection)
            self.is_runing = False
    
    def on_new_connection(self):
        client = self.server.nextPendingConnection()
        if client and self.main_window:
            client.waitForReadyRead(100)
            data = client.readAll().data().decode()
            if data == "show":
                self.main_window.showNormal()
                self.main_window.raise_()
                self.main_window.activateWindow()


class MainWindow(QMainWindow):

    log_signal = pyqtSignal(str)  # 创建一个信号，用于更新日志文本框

    def __init__(self):
        super().__init__()

        self.mVIdPIds = ["VID:1A86/PID:7523", "VID:1A86/PID:55D3", "VID:1A86/PID:55E9"]
        
        self.icon = QIcon(self.resource_path("icon.ico"))
        self.setWindowTitle(f"同步助手 v{VERSION}")
        self.setWindowIcon(self.icon)
        self.setGeometry(100, 100, 600, 400)
        # self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint)  # 仅保留最小化按钮
        
        self.tray = QSystemTrayIcon()
        self.tray.setIcon(self.icon)
        self.tray.setVisible(True)
        
        menu = QMenu()
        show_action = QAction("显示窗口", menu)
        quit_action = QAction("退出程序", menu)
        # 添加分隔线
        menu.addAction(show_action)
        menu.addSeparator()
        menu.addAction(quit_action)
        
        show_action.triggered.connect(self.show_window)
        quit_action.triggered.connect(self.exit_app)
        self.tray.setContextMenu(menu)
        
        self.tray.activated.connect(self.on_tray_activated)
        
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        self.layout = QVBoxLayout()
        self.central_widget.setLayout(self.layout)

        self.status_label = QLabel("状态: 设备未连接")
        self.status_label.setStyleSheet("color: red;")  # 颜色名称
        self.layout.addWidget(self.status_label)

        # self.baudrate_label = QLabel("波特率:")
        # self.layout.addWidget(self.baudrate_label)

        # self.baudrate_combo = QComboBox()
        # self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200", "230400", "921600", "1000000", "2000000", "6000000"])
        # self.baudrate_combo.setCurrentText("1000000")
        # self.layout.addWidget(self.baudrate_combo)
        
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.layout.addWidget(self.log_text)

        self.open_folder_button = QPushButton("打开已接收文件文件夹")
        self.open_folder_button.clicked.connect(self.open_received_files_folder)
        self.layout.addWidget(self.open_folder_button)

        self.serial_thread = None
        self.device_monitor_thread = None
        self.observer = None
        self.exec_thread = None
        
        # 初始化代码...
        self.log_signal.connect(self.update_log_text)  # 将信号连接到处理函数
        
        listener = keyboard.GlobalHotKeys({
            '<ctrl>+<shift>+<alt>+c' : self.hotkey
        })
        listener.start()

        self.start_device_monitoring()
        self.start_serial_device_monitor()
        
        self.center()
        
        self.add_to_startup()
        
    
    def hotkey(self):
        self.task_exc_status()
    
    def update_log_text(self, text):
        """此方法在接收到日志信息时被调用"""
        self.log_text.append(f"{datetime_str()} - {text}")  # 将日志文本添加到 QTextEdit
    
    def add_to_startup(self, app_name = "main", exe_path = None):
        if exe_path is None:
            exe_path = sys.executable
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE)
        winreg.SetValueEx(key, app_name, 0, winreg.REG_SZ, exe_path)
        key.Close()
        

    def show_window(self):
        print("show_window")
        self.show()
        self.raise_()
        self.activateWindow()
    
    def exit_app(self):
        self.tray.hide()
        if self.serial_thread:
            self.serial_thread.stop()
            self.serial_thread.wait()
        if self.device_monitor_thread:
            self.device_monitor_thread.stop()
            self.device_monitor_thread.wait()
        if self.observer:
            self.observer.stop()
            self.observer.join()
        QApplication.quit()

    def on_tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            print(f"on_tray_activated")
            self.show_window()
    
    def resource_path(self, relative_path):
        if hasattr(sys, "_MEIPASS"):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.abspath("."), relative_path)

    def check_serial_devices(self):
        ports = serial.tools.list_ports.comports()
        device_found = False
        for port in ports:
            mVidPid = None
            vid = port.vid
            pid = port.pid
            if vid != None and pid != None:
                mVidPid = "VID:{:04X}/PID:{:04X}".format(vid, pid)
                print(mVidPid)
            if mVidPid in self.mVIdPIds:
                self.status_label.setText(f"状态: 等待连接 {port.device} ...")
                self.status_label.setStyleSheet("color: orange;")  # 颜色名称
                self.update_log_text(f"找到串行设备: {port.device} 端口")
                # self.update_log_text(f"   -- {port.description}")
                # self.update_log_text(f"   -- {mVidPid}")
                self.start_serial_monitor(port.device)
                device_found = True
                if self.device_monitor_thread:
                    self.device_monitor_thread.stop()
                    self.device_monitor_thread.wait()
                    self.device_monitor_thread = None
                break
        
        if not device_found:
            self.status_label.setText("状态: 设备未连接")
            self.status_label.setStyleSheet("color: red;")  # 颜色名称
            if self.serial_thread:
                self.serial_thread.stop()
                self.serial_thread.wait()
                self.serial_thread = None
    
    def start_serial_monitor(self, port_name):
        if self.serial_thread:
            self.serial_thread.stop()
            self.serial_thread.wait()
        config, errorMsg = load_config()
        if errorMsg:
            self.update_log_text(errorMsg)
        baudrate = config.get("baudrate", 15000000)
        self.serial_thread = SerialMonitorThread(port_name, baudrate)
        self.serial_thread.status.connect(self.update)
        self.serial_thread.data_received.connect(self.update_log_text)
        self.serial_thread.file_complete.connect(self.on_file_complete)
        self.serial_thread.task_signal.connect(self.task_exc_status)
        
        self.serial_thread.request_check_devices.connect(self.start_serial_device_monitor)
        self.serial_thread.start()

    def update(self, text):
        self.status_label.setText(text)
        self.status_label.setStyleSheet("color: green;")  # 颜色名称


    def safe_json_parse(self, json_str):
        """
        安全解析包含Unicode转义字符的JSON字符串
        参数：
            json_str（str）- 要解析的JSON字符串
        返回：
            解析后的JSON对象
        异常：
            ValueError：当JSON格式完全无效时抛出Exception
        """
        try:
            # 尝试使用标准JSON解析器解析JSON字符串
            return json.loads(json_str)
        except Exception as e:
            # 如果解析失败，尝试使用Unicode转义字符解析
            if "Invalid" in str(e):
                # 处理Unicode转义字符
                try:
                    #替换所有Unicode转义字符
                    cleaned = json_str.encode("utf-8").decode("unicode_escape")
                    # 再次尝试使用标准JSON解析器解析JSON字符串
                    return json.loads(cleaned)
                except:
                    # 最终回退方案：逐字符检查
                    return json.loads(json_str.encode("utf-8").decode("ascii", "ignore"))
            raise


    def on_file_complete(self, data_str):
        try:
            print(f"on_file_complete: {self.safe_json_parse(data_str)}")
            data = self.safe_json_parse(data_str).get("data")
            if not data:
                self.update_log_text(f"文件接收失败: {data_str}")
                return
            file_path = data.get("file_path")
            print(f"file_path on_file_complete: {file_path}")
            extend = data.get("extend")
            extensions  = data.get("extensions")
            tasks = data.get("tasks")
            source = data.get("source")
            if not file_path:
                self.update_log_text(f"文件接收失败: {file_path}")
                return
            self.update_log_text(f"文件已保存: {file_path}")
            self.tray.showMessage(
                "提示",
                f"文件已保存{file_path}",
                QSystemTrayIcon.Information,
                500
            )
        
            self.exec_thread = ExecuteFlowThread(extend, file_path, extensions, tasks, source)
            self.exec_thread.log_signal.connect(self.update_log_text)
            self.exec_thread.message_signal.connect(self.on_show_message)
            self.exec_thread.start()
        
        except Exception as e:
            self.update_log_text(f"解析数据失败 on_file_complete: {e}")

    def task_exc_status(self, running_task):
        if self.exec_thread:
            self.exec_thread.stop()
            self.exec_thread.wait()

    def on_show_message(self, msg):
        self.tray.showMessage(
            "提示",
            msg,
            QSystemTrayIcon.Information,
            500
        )
    
    def open_received_files_folder(self):
        
        folder_path = os.path.join(os.getcwd(), "received_files")
        print(f"open_received_files_folder: {folder_path}")
        if not os.path.exists(folder_path):
            os.makedirs(folder_path, exist_ok=False)
        os.startfile(folder_path)
        
    
    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())


    def start_device_monitoring(self):
        if platform.system() == "Windows":
            # Windows 系统下跳过 /dev 目录监控
            return
        self.observer = Observer()
        event_handler = SerialDeviceHandler(self)
        self.observer.schedule(event_handler, "/dev", recursive=False)
        self.observer.start()

    def start_serial_device_monitor(self):
        if self.device_monitor_thread:
            self.device_monitor_thread.stop()
            self.device_monitor_thread.wait()
        self.device_monitor_thread = SerialDeviceMonitorThread()
        self.device_monitor_thread.request_check_devices.connect(self.check_serial_devices)
        self.device_monitor_thread.start()

    def closeEvent(self, event):
        print("closeEvent")
        event.ignore()
        self.hide()
        self.tray.showMessage(
            "托盘提示",
            "程序已经最小化到托盘，双击图标或右键菜单显示窗口",
            QSystemTrayIcon.Information,
            500
        )

if __name__ == "__main__":
    
    app = QApplication(sys.argv)
    window = MainWindow()
    single = SingleInstantce(APP_KEY, window)
    if single.is_runing:
        # QMessageBox.warning(None, "提示", "程序已经在运行中！")
        sys.exit(0)
    # 后台启动 websocket 协程（不会阻塞主线程）
    start_server_in_thread(window)
    window.show()
    sys.exit(app.exec_())